用法研究所 - uv
What and Why
uv
是由 Astral 开发的 Python 工具。在一定程度上是可以取代 pip
、poetry
、pyenv
、twine
、virtualenv
等工具,主要用于做 Python 依赖管理和 Python 版本管理。通过 uv
命令可以完成从运行单个 Python 脚本、项目依赖管理(依赖声明和版本锁定)、虚拟环境管理等几乎所有维护 Python 项目需要的功能。
之前的工具都只负责部分功能,如 pyenv
负责系统上的 Python 版本管理;virtualenv
负责虚拟 Python 环境的创建;pip
负责依赖管理;uv
实现了所有这些功能。当然 poetry
也实现了这些功能,但 uv
说它比 poetry
更快。
为什么 uv 更快?
用 Rust 重写了包解析器;在包管理上完全脱离 Python 本身工具链,甚至不会启动 Python,在 Rust 层面处理问题;用了 Rust 相比 Python 就有了并发优势(Python 有 GIL 限制)。相比之下,像
poetry
这样的工具仍基于 Python 运行,受限于解释器启动时间、单线程执行等因素,会比较慢。
注意事项
uv
并不等同于pyenv
(用于管理系统中的多个 Python 版本)或twine
(用于将包发布到 PyPI),这些功能目前还不在uv
的职责范围内。uv
只能管理通过uv run
运行时使用的 Python 版本。
Python 版本管理
当我们说 Python 版本管理时,我们就是在说 Python 可执行文件和标准库等完整配套的管理。先看使用方法
1 | 展示第一个选中的 Python 安装位置。一般来说这就是当前生效的 python 版本 |
确定 Python 版本 ref
我们用 uv run
运行脚本时,使用 uv add
安装依赖时,都需要用到 Python 环境,uv
按照如下规则确定使用哪个版本的 Python 环境。
- 当前目录下的
.python-version
文件指定的版本 - 父目录(不超过当前项目目录)下的
.python-version
文件指定的版本 - 用户目录下的
.python-version
文件指定的版本 - 如果都没有,则不指定,使用当前系统中生效的 Python 版本
发现 Python 版本 ref
在确定了 Python 版本后,uv
按照如下规则搜索符合版本的执行环境
- 首先尝试虚拟环境的发现规则
- 优先尝试
VIRTUAL_ENV
变量指定的虚拟环境 - 其次尝试
CONDA_PREFIX
指定的虚拟环境 - 最后尝试当前目录下的
.venv
或父目录下的.venv
环境
- 优先尝试
- 然后尝试自己的发现规则
- 首先搜索
uv
自己的 Python 安装目录下的版本 - 其次检查系统中符合要求的 Python 版本
- 首先搜索
uv python find
得到的结果就是按照上述规则找到的第一个符合要求的 Python 版本路径。如果我们使用 uv python pin
改变当前目录下的 .python-version
文件所记录的版本,会发现 uv python find
得到的结果已经更新了。**你可能会有疑问,之前创建的 venv
还是旧版本,uv run
会发生什么,答案是执行 uv run
时,uv
会将旧的 venv
移除,创建符合新版本的 venv
**。下面的例子有展示
1 | 固定 3.10 版本 |
pyproject.toml 的 requires-python
的作用.python-version
约束了当前文件夹下的固定的 Python 版本,而 pyproject.toml
下的 requires-python
约束了当前项目(其实也是当前文件夹)的 Python 版本。我们应该保持二者兼容,当不兼容时,执行 uv sync
或 uv run
等命令时会报错,如下
1 | uv run -- python -c "import sys; print(sys.executable)"; |
依赖管理
1 | 添加依赖 |
添加依赖时,依赖可以有多种约束形式。详情参考手册
默认源也可以通过
UV_DEFAULT_INDEX
环境变量设置
依赖安装到哪里去了
当我们执行 uv add
时,会在 pyproject.toml
中添加依赖声明,然后安装依赖到当前环境。当前环境就是按照Python 版本管理所述规则确定的环境。
uv lock
时版本如何确定uv lock
当前项目的依赖写到 uv.lock
文件,当前项目的依赖是指 Python 运行环境中的依赖版本。
uv sync
做了什么uv sync
将 uv.lock
文件中声明的依赖安装到当前项目环境
pyproject.toml
、uv.lock
与当前 Python 环境已安装依赖的关系
如果 pyproject.toml
、uv.lock
都有声明依赖 A,但版本不兼容;当前 Python 环境也有 A,版本与前两个也不兼容,此时会发生什么?应该怎么解决这种状态。
- 首先不建议直接修改
pyproject.toml
文件,而是应该使用uv add
、uv remove
等命令去统一管理当前环境的依赖。 - 其次,如果真的出现了这种情况,我们应该按照如下原则处理
- 如果想要依照
uv.lock
中的版本,执行uv sync
即可将当前环境和pyproject.toml
中的版本进行统一 - 如果想要指定版本,则应该通过
uv add xxx
来更新这三个地方的依赖
- 如果想要依照
uv add
会更新 uv.lock
吗?如果会,uv lock
有什么用
会更新 uv.lock
,uv lock
命令主要用于手动生成或更新锁文件。
lock 和 sync 是自动执行的
当 uv run
之类的命令运行时,会自动 sync
并 lock
。
在 Python 版本管理中我们也有看到,uv run
之前也会同步更新 Python 版本到符合要求的状态。
项目管理
1 | 在当前文件夹下新建项目,项目名就是当前的文件夹名 |
项目内会包含几个文件
.python-version
前面已经说过了pyproject.toml
为项目信息描述文件uv.lock
刚生成时没有,但安装依赖后就会有。通常来说不需要太关注uv.lock
,它会被自动创建和更新。
pyproject.toml
一个常规的配置如下,它只说明了项目信息、依赖、工具参数。
1 | [project] |
更多配置参见手册,有一点值得注意的是它可以定义一个 entrypoint
,可以通过 uv run
来执行。
例如有了如下定义,uv run hello
就会执行 example:hello
1 | [project.scripts] |
有关
uv index
的更多配置,参考手册
uv run
uv run
就是一个增强版的 python 命令。
uv run main.py
- 如果当前目录不是 uv 项目,则直接用
uv
生效的环境运行main.py
- 如果当前目录是 uv 项目,则会自动安装依赖然后运行
main.py
- 如果当前目录不是 uv 项目,则直接用
uv run --with requests demo.py
- 这是声明运行
demo.py
时临时安装requests
依赖。该临时依赖在命令执行完毕后会自动清理
- 这是声明运行
创建独立脚本uv run
一个很强的功能是可以支持在单个脚本中声明依赖、python 版本等,如下。
1 | #!/usr/bin/env -S uv run --script |
uv tool
与 uvx
uv tool
用来执行之前通过 python install
安装的工具。不同的是,python install
后是长久地安装工具(会安装后加入 PATH),而 uv tool
把工具安装到 uv
缓存中,不会对宿主机产生影响。
这在只需要执行一两次某些工具时非常有用。
uvx
只是 uv tool run
命令的别名。
1 | 临时执行 ruff 命令 |
实践
新项目如何用 uv
管理
1 | 创建项目 |
老项目如何引入 uv
1 | 初始化项目 |